<?php

declare(strict_types=1);

namespace app\service\settlement;

use app\model\CommissionLedger;
use app\model\Order;
use app\model\OrderRefund;
use app\model\Org;
use app\model\OrgMember;
use app\model\SellerStatement;
use app\model\SellerStatementItem;
use app\service\notify\CreateNotifyService;
use app\util\GenNoUtil;
use InvalidArgumentException;
use think\Collection;
use think\facade\Db;
use think\Paginator;

class StatementService
{
    public function __construct(private CreateNotifyService $notifyService)
    {
    }

    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = SellerStatement::with(['org'])
            ->order('id', 'desc');

        if (!empty($filters['seller_org_id'])) {
            $query->where('seller_org_id', (int) $filters['seller_org_id']);
        }

        if (array_key_exists('status', $filters) && $filters['status'] !== null && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('statement_no', '%' . $keyword . '%')
                    ->whereOr('remark', 'like', '%' . $keyword . '%');
            });
        }

        if (!empty($filters['period_start'])) {
            $query->where('period_end', '>=', $filters['period_start']);
        }

        if (!empty($filters['period_end'])) {
            $query->where('period_start', '<=', $filters['period_end']);
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function get(int $id): SellerStatement
    {
        $statement = SellerStatement::with(['items', 'invoices', 'payouts', 'org'])->find($id);
        if (!$statement) {
            throw new InvalidArgumentException('结算账单不存在');
        }

        return $statement;
    }

    public function generate(array $payload): SellerStatement
    {
        $sellerOrgId = (int) ($payload['seller_org_id'] ?? 0);
        if ($sellerOrgId <= 0) {
            throw new InvalidArgumentException('卖家组织参数不正确');
        }

        if (Org::where('id', $sellerOrgId)->count() === 0) {
            throw new InvalidArgumentException('卖家组织不存在');
        }

        $periodStart = (string) ($payload['period_start'] ?? '');
        $periodEnd = (string) ($payload['period_end'] ?? '');
        if ($periodStart === '' || $periodEnd === '') {
            throw new InvalidArgumentException('结算周期不能为空');
        }

        $startTs = strtotime($periodStart);
        $endTs = strtotime($periodEnd);
        if ($startTs === false || $endTs === false || $startTs > $endTs) {
            throw new InvalidArgumentException('结算周期不合法');
        }

        $orderIdsFilter = [];
        if (!empty($payload['order_ids']) && is_array($payload['order_ids'])) {
            $orderIdsFilter = array_values(array_unique(array_map('intval', $payload['order_ids'])));
        }

        $orders = $this->fetchOrdersForStatement($sellerOrgId, $periodStart, $periodEnd, $orderIdsFilter);
        if ($orders->isEmpty()) {
            throw new InvalidArgumentException('结算周期内没有可用订单');
        }

        $orderIds = array_map('intval', $orders->column('id'));
        $duplicatedOrder = SellerStatementItem::whereIn('order_id', $orderIds)->value('order_id');
        if ($duplicatedOrder) {
            throw new InvalidArgumentException('存在已生成账单的订单（ID: ' . $duplicatedOrder . '）');
        }

        $summary = $this->buildStatementSummary($orders);

        $remark = (string) ($payload['remark'] ?? '');

        $statement = Db::transaction(function () use ($sellerOrgId, $periodStart, $periodEnd, $summary, $remark) {
            $now = date('Y-m-d H:i:s');

            /** @var SellerStatement $statement */
            $statement = SellerStatement::create([
                'statement_no' => GenNoUtil::statement(),
                'seller_org_id' => $sellerOrgId,
                'period_start' => $periodStart,
                'period_end' => $periodEnd,
                'total_order_amount' => $summary['total_order_amount'],
                'total_refund_amount' => $summary['total_refund_amount'],
                'commission_amount' => $summary['commission_amount'],
                'payable_amount' => $summary['payable_amount'],
                'paid_amount' => 0,
                'status' => SellerStatement::STATUS_DRAFT,
                'generated_at' => $now,
                'remark' => $remark,
            ]);

            foreach ($summary['items'] as $item) {
                $item['statement_id'] = (int) $statement->id;
                SellerStatementItem::create($item);
            }

            CommissionLedger::whereIn('order_id', $summary['order_ids'])
                ->where('status', CommissionLedger::STATUS_PENDING)
                ->update([
                    'status' => CommissionLedger::STATUS_SETTLED,
                    'statement_id' => (int) $statement->id,
                    'settled_at' => $now,
                ]);

            return SellerStatement::with(['items', 'org'])->find((int) $statement->id);
        });

        return $statement;
    }

    public function update(SellerStatement $statement, array $payload): SellerStatement
    {
        $fields = [];
        if (array_key_exists('remark', $payload)) {
            $fields['remark'] = (string) $payload['remark'];
        }

        if (array_key_exists('paid_amount', $payload)) {
            $paidAmount = round(max((float) $payload['paid_amount'], 0), 2);
            $fields['paid_amount'] = $paidAmount;
        }

        $targetStatus = array_key_exists('status', $payload) ? (int) $payload['status'] : null;

        if ($targetStatus !== null) {
            return $this->transition($statement, $targetStatus, $fields, true);
        }

        if ($fields === []) {
            return $statement->refresh();
        }

        $updated = Db::transaction(function () use ($statement, $fields) {
            /** @var SellerStatement|null $locked */
            $locked = SellerStatement::lock(true)->find((int) $statement->id);
            if (!$locked) {
                throw new InvalidArgumentException('结算账单不存在');
            }

            if (isset($fields['remark'])) {
                $locked->remark = $fields['remark'];
            }

            if (isset($fields['paid_amount'])) {
                if ($fields['paid_amount'] - (float) $locked->payable_amount > 0.01) {
                    throw new InvalidArgumentException('已付金额不能超过应付金额');
                }
                $locked->paid_amount = $fields['paid_amount'];
            }

            $locked->save();

            return SellerStatement::with(['items', 'invoices', 'payouts', 'org'])->find((int) $locked->id);
        });

        return $updated;
    }

    public function transition(SellerStatement $statement, int $targetStatus, array $additional = [], bool $notify = true): SellerStatement
    {
        $updated = Db::transaction(function () use ($statement, $targetStatus, $additional) {
            /** @var SellerStatement|null $locked */
            $locked = SellerStatement::lock(true)->find((int) $statement->id);
            if (!$locked) {
                throw new InvalidArgumentException('结算账单不存在');
            }

            $currentStatus = (int) $locked->status;
            if ($currentStatus === $targetStatus) {
                return SellerStatement::with(['items', 'invoices', 'payouts', 'org'])->find((int) $locked->id);
            }

            $this->assertStatusTransition($currentStatus, $targetStatus);

            $now = date('Y-m-d H:i:s');

            if (isset($additional['remark'])) {
                $locked->remark = (string) $additional['remark'];
            }

            if ($targetStatus === SellerStatement::STATUS_PAID) {
                if (!isset($additional['paid_amount'])) {
                    throw new InvalidArgumentException('请提供已付金额');
                }
                $paidAmount = round(max((float) $additional['paid_amount'], 0), 2);
                if ($paidAmount - (float) $locked->payable_amount > 0.01) {
                    throw new InvalidArgumentException('已付金额不能超过应付金额');
                }
                $locked->paid_amount = $paidAmount;
            } elseif (isset($additional['paid_amount'])) {
                throw new InvalidArgumentException('当前状态无法更新已付金额');
            }

            switch ($targetStatus) {
                case SellerStatement::STATUS_CONFIRMING:
                    $locked->confirmed_at = $locked->confirmed_at ?: $now;
                    break;
                case SellerStatement::STATUS_WAIT_INVOICE:
                    $locked->confirmed_at = $locked->confirmed_at ?: $now;
                    break;
                case SellerStatement::STATUS_WAIT_PAYOUT:
                    $locked->invoiced_at = $now;
                    break;
                case SellerStatement::STATUS_PAID:
                    $locked->paid_at = $now;
                    break;
            }

            $locked->status = $targetStatus;
            $locked->save();

            if ($targetStatus === SellerStatement::STATUS_PAID) {
                CommissionLedger::where('statement_id', $locked->id)
                    ->where('status', '<>', CommissionLedger::STATUS_PAID)
                    ->update([
                        'status' => CommissionLedger::STATUS_PAID,
                        'paid_at' => $now,
                    ]);
            }

            return SellerStatement::with(['items', 'invoices', 'payouts', 'org'])->find((int) $locked->id);
        });

        if ($notify && $targetStatus === SellerStatement::STATUS_WAIT_INVOICE) {
            $this->notifyInvoicePending($updated);
        }

        return $updated;
    }

    private function fetchOrdersForStatement(int $sellerOrgId, string $periodStart, string $periodEnd, array $orderIds): Collection
    {
        $query = Order::where('seller_org_id', $sellerOrgId)
            ->where('status', '>=', Order::STATUS_COMPLETED);

        $query->where('finished_at', '>=', $periodStart . ' 00:00:00')
            ->where('finished_at', '<=', $periodEnd . ' 23:59:59');

        if ($orderIds !== []) {
            $query->whereIn('id', $orderIds);
        }

        return $query->select();
    }

    private function buildStatementSummary(Collection $orders): array
    {
        $orderIds = array_map('intval', $orders->column('id'));

        $refundRecords = OrderRefund::whereIn('order_id', $orderIds)
            ->where('status', OrderRefund::STATUS_COMPLETED)
            ->select();

        $refundTotals = [];
        foreach ($refundRecords as $refund) {
            $refundTotals[(int) $refund->order_id] = ($refundTotals[(int) $refund->order_id] ?? 0) + (float) $refund->buyer_amount;
        }

        $items = [];
        $totalOrder = 0.0;
        $totalRefund = 0.0;
        $totalCommission = 0.0;
        $totalNet = 0.0;

        foreach ($orders as $order) {
            $orderAmount = round((float) $order->seller_receivable_amount, 2);
            $commission = round((float) $order->platform_commission_amount, 2);
            $refundAmount = round($refundTotals[(int) $order->id] ?? 0.0, 2);
            $netAmount = round(max($orderAmount - $refundAmount, 0), 2);

            $items[] = [
                'order_id' => (int) $order->id,
                'order_no' => (string) $order->order_no,
                'order_amount' => $orderAmount,
                'refund_amount' => $refundAmount,
                'commission_amount' => $commission,
                'net_amount' => $netAmount,
                'status' => 1,
                'remark' => '',
            ];

            $totalOrder += $orderAmount;
            $totalRefund += $refundAmount;
            $totalCommission += $commission;
            $totalNet += $netAmount;
        }

        return [
            'order_ids' => $orderIds,
            'items' => $items,
            'total_order_amount' => round($totalOrder, 2),
            'total_refund_amount' => round($totalRefund, 2),
            'commission_amount' => round($totalCommission, 2),
            'payable_amount' => round($totalNet, 2),
        ];
    }

    private function assertStatusTransition(int $current, int $target): void
    {
        $allowed = [
            SellerStatement::STATUS_DRAFT => [
                SellerStatement::STATUS_CONFIRMING,
                SellerStatement::STATUS_WAIT_INVOICE,
            ],
            SellerStatement::STATUS_CONFIRMING => [
                SellerStatement::STATUS_WAIT_INVOICE,
            ],
            SellerStatement::STATUS_WAIT_INVOICE => [
                SellerStatement::STATUS_WAIT_PAYOUT,
            ],
            SellerStatement::STATUS_WAIT_PAYOUT => [
                SellerStatement::STATUS_PAID,
            ],
            SellerStatement::STATUS_PAID => [],
        ];

        if (!in_array($target, $allowed[$current] ?? [], true)) {
            throw new InvalidArgumentException('账单状态不允许该操作');
        }
    }

    private function notifyInvoicePending(SellerStatement $statement): void
    {
        $targets = $this->collectOrgTargets((int) $statement->seller_org_id);
        if ($targets === []) {
            return;
        }

        $this->notifyService->add($targets, 'settlement_invoice_pending', [
            'statement_no' => $statement->statement_no,
        ]);
    }

    private function collectOrgTargets(int $orgId): array
    {
        $userIds = OrgMember::where('org_id', $orgId)
            ->whereIn('role', [OrgMember::ROLE_OWNER, OrgMember::ROLE_ADMIN])
            ->column('user_id');

        $targets = [];
        foreach ($userIds as $id) {
            if ($id) {
                $targets[] = [
                    'id' => (int) $id,
                    'type' => 'seller',
                ];
            }
        }

        return $targets;
    }
}
